#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include <stdio.h>
#include <curand.h>
#include <curand_kernel.h>
#include "heap.cuh"
#include "bench.cuh"


FILE *fp;

int main(int argc, char *argv[])
{

	char path[256];
	char cum[256];

	//Output log file (instance run)
	strcpy(path,OUTPATH);
	strcat(path,"_");
	strcat(path, argv[1]);
	strcat(path,".txt");
	
	//Output cum file (experiment codename)
	strcpy(cum,CUMPATH);
	strcat(cum,"_");
	strcat(cum, argv[0]);
	strcat(cum,".txt");

	//Output	
	fp = fopen(path,"w");
	fprintf(fp,"\n Start \n");
	time_t rawtime;
	time ( &rawtime );
	fprintf (fp,"The current local time is: %s", ctime (&rawtime) );

	
	//log
	fprintf(fp,"VECTORSIZE %d\n",VECTORSIZE);
	fprintf(fp," HISTSIZE %d\n",HISTSIZE);
	fprintf(fp," HEAPSIZE %d\n",HEAPSIZE);
	fprintf(fp," BINS %d\n",BINS);
	fprintf(fp," SEED %d\n",SEED);
	fprintf(fp," TIME %d\n",TIME);
	fprintf(fp," LIFETIME %d\n",LIFETIME);
	fprintf(fp,"\n");

	//Set Sigma
	SIGMA = atof(argv[3]);
	 
	 
	//Hello
	fprintf(fp,"Running %d threads on %d blocks.  \n",HISTSIZE,HEAPSIZE);


	//Cuda variables
	cudaError_t cudaStatus;	
	curandState *devStates;
		
	
	//Counters
	int i;
	int j;
	int k;
	int l;

	//Mutation counters
	int mc;
	int ms;
	
	//Seed the random number generator
	int seed;
	srand((unsigned)time(0));
	
	//Population
	heap * HEAP;
	heapElement *devHeapElementArray;
	heapElement heapElementArray[HEAPSIZE+1];
	

	//Heap init, connect pointers
	HEAP = (heap *)malloc(sizeof(heap));
	//HEAP->elementArray[0] = (heapElement *)malloc(sizeof(heapElement));


	for(i=0; i<=HEAPSIZE;i++){
		HEAP -> elementArray[i] = &heapElementArray[i];
		heapElementInit(HEAP -> elementArray[i],i);
	}

	//Load from previous
	if(LOAD == 1)	heapLoad(HEAP);


	// Choose which GPU to run on, change this on a multi-GPU system.
    cudaStatus = cudaSetDevice(0);
    if (cudaStatus != cudaSuccess) {
        fprintf(stderr, "cudaSetDevice failed!  Do you have a CUDA-capable GPU installed?");
        goto Error2;
    }

	//Allocate memory on device;
	cudaStatus = cudaMalloc((void **)&devStates, HISTSIZE * sizeof(curandState));
	if (cudaStatus != cudaSuccess) {
        fprintf(stderr, "cudaMalloc failed!");
        goto Error2;
    }

	cudaStatus = cudaMalloc((void**)&devHeapElementArray, (1+HEAPSIZE)*sizeof(heapElement));
			if (cudaStatus != cudaSuccess) {
			fprintf(stderr, "cudaMalloc failed!");
			goto Error2;
		}

	fprintf(fp,"Allocating %d x %d bytes = %d bytes \n",HEAPSIZE+1, sizeof(heapElement), (1+HEAPSIZE)*sizeof(heapElement));

	//Main loop
	for(k=0; k<LIFETIME; k++){

		fseek(fp, -45, SEEK_END);
		fflush(fp);
		fclose(fp);
		fp = fopen(path,"a");

		fprintf(stderr, "Generation %d\n", k);
		fprintf(fp,"#############\n");
		fprintf(fp,"GENERATION %d\n",k);
		fprintf(fp,"#############\n");



		//Copy array to device
		cudaStatus = cudaMemcpy(devHeapElementArray, heapElementArray, (1+HEAPSIZE)*sizeof(heapElement), cudaMemcpyHostToDevice);
		if (cudaStatus != cudaSuccess) { fprintf(stderr, "cudaMemcpy to device failed!\n"); goto Error2; }

	
		//Performance measurement
		float elapsedTime;
		FILE *cfp;
		cudaEvent_t start, stop;
		cudaEventCreate(&start);
		cudaEventCreate(&stop);
		cudaEventRecord(start,0);

		seed = floor((1.0*rand()/RAND_MAX) * (RAND_MAX-128*HISTSIZE));
		//seed = SEED;
		ssaKernel9<<<1+HEAPSIZE,HISTSIZE>>>(devStates,devHeapElementArray,seed);

		cudaDeviceSynchronize();
		cudaEventRecord(stop, 0);
		cudaEventSynchronize(stop);
		cudaEventElapsedTime(&elapsedTime, start, stop);

		
		//Timelog
		cfp = fopen("D:\\Dropbox\\Craggz\\Out\\timelog_cuda.txt", "a");
		fprintf(cfp, "%d %d %f \n", HEAPSIZE, HISTSIZE, elapsedTime);
		fclose(cfp);

		//Get heap back from device;
		cudaStatus = cudaMemcpy(heapElementArray, devHeapElementArray, (HEAPSIZE+1)*sizeof(heapElement), cudaMemcpyDeviceToHost);
		if (cudaStatus != cudaSuccess) { fprintf(stderr, "cudaMemcpy from device failed!  Errorcode: %d\n", cudaStatus); goto Error2; }
	
	
		fprintf(fp,"Heap array back from device\n");


		//Calculate fitness
		mc = 0;
		ms = 0;
		for(i=1; i<=HEAPSIZE;i++){
			//fprintf(fp,"%d \n",i);
			heapElementFitness(HEAP->elementArray[i]);
			mc += HEAP->elementArray[i]->mutCount;
			ms += HEAP->elementArray[i]->mutSuccess;
		}
	
		//heapPrint(HEAP);
		fprintf(fp,"Heaping...\n");
		heapBuild(HEAP);
		heapSort(HEAP);
		heapPrint(HEAP);

		//Save the heap to file.
		//heapSave(HEAP, k);


		//Mutations fuzzy logic.
		/*
		if(k>1){
			fprintf(fp,"\n");
			fprintf(fp,"Sucessful mutations: %f\n", (1.0*ms)/mc);
			fprintf(fp,"\n");
			fprintf(fp,"\n");
		} 
		*/

		/*
		if(k>0){
			if ((1.0*ms)/mc < 0.2f) SIGMA *= 1.0f/MDELTA;
			else if ((1.0*ms)/mc > 0.2f) SIGMA *= MDELTA;
		}
		
		fprintf(fp,"SIGMA: %f\n",SIGMA);
		fprintf(fp,"\n");
		*/
	
    
    if (k%2 == 1){

      //Crossover
	    printf("Crossing over... lower half.\n");
      for(i=1; i<=HEAPSIZE/2;i++){
		    j = i + HEAPSIZE/2;
		    l = 1+(int)((i-1.0)*rand()/RAND_MAX);
		    //printf("\t i:%d j:%d l:%d\n", i,j,l);

        /*Single point crossover*/
		    //if(1.0*rand()/RAND_MAX < 0.5) heapElementCrossover(HEAP->elementArray[j],HEAP->elementArray[i],HEAP->elementArray[l]);
		    //else heapElementCrossover(HEAP->elementArray[j],HEAP->elementArray[l],HEAP->elementArray[i]);

        /*N-Point Crossover*/
        if(1.0*rand()/RAND_MAX < 0.5) heapElementCrossoverUni(HEAP->elementArray[j],HEAP->elementArray[i],HEAP->elementArray[l]);
        else heapElementCrossoverUni(HEAP->elementArray[j],HEAP->elementArray[l],HEAP->elementArray[i]);

        /*Asexual reproduction*/
        //Inherit fitness!
        //heapElementCrossoverAsex(HEAP->elementArray[j],HEAP->elementArray[l]);
	    }
	    //fprintf(fp,"\n");
	    //heapPrint(HEAP);
    }

    if (k%2 == 0){
      //Mutation
      fprintf(fp,"Mutating...all but the boss.\n");
      //for(i=2; i<=HEAPSIZE;i++) heapElementMutate(HEAP->elementArray[i]);
      heapMutateMoga(HEAP);
      //fprintf(fp,"\n");
      //heapPrint(HEAP);
		}
		

			
	}

	goto Reset;
	
	
	
	
Reset:
    cudaStatus = cudaDeviceReset();
    if (cudaStatus != cudaSuccess) {
        fprintf(stderr, "cudaDeviceReset failed!");
        return 1;
    }

Error2:
	cudaFree(devStates);
	cudaFree(devHeapElementArray);
	

	time ( &rawtime );
	fprintf (fp,"Finish. \n The current local time is: %s", ctime (&rawtime) );
	fclose(fp);

	//Save to cum
	FILE *fc;
	fc = fopen(cum,"a");
	fprintf(fc, "%f ", HEAP->elementArray[1]->fitness);
	for(i=0; i<VECTORSIZE-1; i++) fprintf(fc, "%f, ", HEAP->elementArray[1]->vector[i]);
	fprintf(fc, "%f ", HEAP->elementArray[1]->vector[VECTORSIZE-1]);
	fprintf(fc,"\n");
	fclose(fc);

	return 0;

}
